#include <stdio.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <gphoto2/gphoto2-camera.h>
#include <gphoto2/gphoto2.h>
#include "headers/PanberryMain.h"
#include "headers/PanberryRotation.h"
#include <event2/event.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void executeAction(evutil_socket_t fd, short what, void *arg);
void react(evutil_socket_t fd, short what, void *arg);
void term(evutil_socket_t fd, short what, void *arg);
void end(evutil_socket_t fd, short what, void *arg);

int turnOnDiode(int diode);
int turnOffDiode(int diode);
int checkCamera();
void initSystem();
void setStatus(int s);
void tryKill(int pid);

struct event *newCmdEvent, *completedEvent, *termEvent, *endEvent;
struct event_base *base;

int currentAngle = 0;
int cmdFifoFd;
int cmdFifoSFd;

int status;
int previousStatus;

pid_t input = -1;
pid_t pano = -1;
pid_t photo = -1;
pid_t rotating = -1;
pid_t main2 = -1;

int main(int argc, char *argv[])
{
	setStatus(STATUS_EMPTY);
    srand(time(NULL));
	input = fork();
	if (input > 0) {
		initSystem();

		if ((base = event_base_new()) == NULL) {
			fprintf(stderr, "event_base_new: internal error");
			return EXIT_FAILURE;
		}
		//  creating pipe if it doesn't exist
		if (mkfifo(CMD_FIFO, 0777) == -1) {
			if (errno != EEXIST) {
				perror("mkfifo");
				return EXIT_FAILURE;
			}
		}
		if (mkfifo(CMD_FIFO_S, 0777) == -1) {
			if (errno != EEXIST) {
				perror("mkfifo(2)");
				return EXIT_FAILURE;
			}
		}
		if ((cmdFifoFd = open(CMD_FIFO, O_RDWR | O_NONBLOCK)) <= 0) {
			perror("open");
			return EXIT_FAILURE;
		}
		if ((cmdFifoSFd = open(CMD_FIFO_S, O_RDWR | O_NONBLOCK)) <= 0) {
			perror("open");
			return EXIT_FAILURE;
		}
		// attach events to their handlers
		if ((newCmdEvent =
		     event_new(base, cmdFifoFd, EV_READ, executeAction,
			       NULL)) == NULL) {
			fprintf(stderr, "event_new: internal error");
			return EXIT_FAILURE;
		}
		if ((completedEvent =
		     evsignal_new(base, SIGCHLD, react, NULL)) == NULL) {
			fprintf(stderr, "evsignal_new(1): internal error");
			return EXIT_FAILURE;
		}
		if ((termEvent =
		     evsignal_new(base, SIGTERM, end, NULL)) == NULL) {
			fprintf(stderr, "evsignal_new(2): internal error");
			return EXIT_FAILURE;
		}

		if ((endEvent = evsignal_new(base, SIGINT, end, NULL)) == NULL) {
			fprintf(stderr, "evsignal_new(3): internal error");
			return EXIT_FAILURE;
		}
		// activate to monitored set of events
		event_add(newCmdEvent, NULL);
		evsignal_add(completedEvent, NULL);
		evsignal_add(termEvent, NULL);
		evsignal_add(endEvent, NULL);

		
		setStatus(STATUS_BUSY);
            if (checkCamera() == EXIT_FAILURE){
			    setStatus(STATUS_NO_CAMERA);
		    } else setStatus(STATUS_READY);

		// running main loop
		if (event_base_dispatch(base) == -1) {
			fprintf(stderr, "event_base_dispatch: internal error");
			event_free(newCmdEvent);
			event_base_free(base);
			return EXIT_FAILURE;
		}
	}
	if (input == 0) {
		if (execl(PANBERRY_INPUT, PANBERRY_INPUT, (char *)NULL) == -1) {
			perror("execl");
			return;
		}
	}
	return EXIT_SUCCESS;
}

// what if we have a task to do
void executeAction(evutil_socket_t fd, short what, void *bytes)
{
	char data[ACTION_MAXSIZE] = { 0 };
	char angle[4] = { 0 };
	char img[100] = { 0 };
	char tmp[100] = PATH_TEMP;
	int destinationAngle;
	int len;
	int cmd;
	int returnValue, code;

	// read command
	if (read(fd, data, ACTION_MAXSIZE) == -1) {
		perror("read");
		exit(EXIT_FAILURE);
	}
	cmd = (int)strtol(data, NULL, 10);
	switch (cmd) {
	/***********************************************************************************************/
	case ACTION_ROTATE:
		fprintf(stderr, "actio\n");
		if (status == STATUS_ROTATING || status == STATUS_BUSY
		    || status == STATUS_SHOOTING)
			break;
		setStatus(STATUS_ROTATING);

		if (read(fd, data, ACTION_MAXSIZE) == -1) {
			perror("read");
			exit(EXIT_FAILURE);
		}
		destinationAngle = (int)strtol(data, NULL, 10);
		// server command to rotate
		if (destinationAngle < MIN_ANGLE
		    || destinationAngle > MAX_ANGLE)
			destinationAngle = currentAngle;

		sprintf(tmp, "sudo %s %d", PANBERRY_ROTATION, destinationAngle);
		system(tmp);
        setStatus(previousStatus);
		break;
	/***********************************************************************************************/
	case ACTION_ROTATE_R:
		if (status == STATUS_ROTATING) {
			fprintf(stderr, "already rotating\n");
			break;
		}

		if (status == STATUS_BUSY || status == STATUS_SHOOTING
		    || status == STATUS_STITCHING) {
			fprintf(stderr, "busy right now\n");
			break;
		}
		setStatus(STATUS_ROTATING);

		// pushed button to rotate a little bit
		destinationAngle = currentAngle + STEP;
		if (destinationAngle < MIN_ANGLE
		    || destinationAngle > MAX_ANGLE) {
			setStatus(previousStatus);
			break;
		}
		currentAngle = destinationAngle;

		sprintf(tmp, "sudo %s %d relax", PANBERRY_ROTATION,
			destinationAngle);
        
		system(tmp);
        setStatus(previousStatus);
		break;
	/***********************************************************************************************/
	case ACTION_ROTATE_L:

		if (status == STATUS_ROTATING) {
			fprintf(stderr, "already rotating\n");
			break;
		}
		if (status == STATUS_BUSY || status == STATUS_SHOOTING
		    || status == STATUS_STITCHING) {
			fprintf(stderr, "busy right now\n");
			break;
		}
		setStatus(STATUS_ROTATING);
		destinationAngle = currentAngle - STEP;
		if (destinationAngle < MIN_ANGLE
		    || destinationAngle > MAX_ANGLE) {
			setStatus(previousStatus);
			break;
		}
		currentAngle = destinationAngle;
		sprintf(tmp, "sudo %s %d relax", PANBERRY_ROTATION,
			destinationAngle);
        fprintf(stderr, tmp);
		system(tmp);
        setStatus(previousStatus);
		break;
	/***********************************************************************************************/
	case ACTION_STATUS:
		// write to server which doesn't exist at all
		tmp[0] = status;
		if (write(2, tmp, 1) == -1) {
			perror("write");
			exit(EXIT_FAILURE);
		}
		break;
	/***********************************************************************************************/
	case ACTION_START:
        if (status == STATUS_NO_CAMERA || status == STATUS_ERR){
            setStatus(STATUS_BUSY);
            if (checkCamera() == EXIT_FAILURE)
			setStatus(STATUS_NO_CAMERA);
		else
			setStatus(STATUS_READY);
            break;
        }
        
		if (status == STATUS_ROTATING)
			break;

		// check availability to work
		if (status != STATUS_BUSY && status != STATUS_STITCHING
		    && status != STATUS_SHOOTING) {
			setStatus(STATUS_BUSY);


			sprintf(tmp, "rm -f %s/*.jpg", PATH_IMAGES);
			system(tmp);

			setStatus(STATUS_SHOOTING);
			int i;
			destinationAngle = MIN_ANGLE + 30;
			main2 = fork();
			if (main2 == 0) {
                event_del(newCmdEvent);
                
				while (destinationAngle <= MAX_ANGLE) {

					char buff[100];
                    int retval;
					fprintf(stderr, "cycle  %d\n",
						destinationAngle);
					sprintf(buff, "sudo %s %d",
						PANBERRY_ROTATION,
						destinationAngle);

					system(buff);
                

					currentAngle = destinationAngle;

					sprintf(buff, "sudo %s %s/%d.jpg",
						PANBERRY_PHOTO, PATH_IMAGES, i);
					fprintf(stderr, "%s\n", buff);
					retval = system(buff);
	                if (WEXITSTATUS(retval) != EXIT_SUCCESS) {
		                fprintf(stderr, "PanberryPhoto error %d\n",WEXITSTATUS(retval));
		                exit(EXIT_FAILURE);
	                } 

					destinationAngle += STEP;
					i++;
				}
                int retval;
				system("gpio pwm 1 0");
				// snitching here is produced
				setStatus(STATUS_STITCHING);

				retval = system(PANBERRY_PANO);
	                if (WEXITSTATUS(retval) != EXIT_SUCCESS) {
		                fprintf(stderr, "PanberryPano error %d\n", WEXITSTATUS(retval));
		                exit(EXIT_FAILURE);
	                } 
				
				exit(EXIT_SUCCESS);
			}

           
			break;
            
		}
		// else goto ACTION_TERMINATE
	/***********************************************************************************************/
	case ACTION_TERMINATE:

        if (main2 == 0)
            exit(0);

		fprintf(stderr, "terminating the capture\n");
		setStatus(STATUS_TERMINATING);
		tryKill(main2);
        system("gpio pwm 1 0");
        sleep(5);
			setStatus(STATUS_NO_CAMERA);
		
		
		break;
	/***********************************************************************************************/
	case NO_ACTION:
		break;
	/***********************************************************************************************/
		// we ignore this
	default:
		//fprintf(stderr, "bad data:  %d\n", cmd);
		break;
	}
	event_add(newCmdEvent, NULL);
}

void react(evutil_socket_t fd, short what, void *arg)
{
	//fprintf(stderr, "rea\n");
	// here we just define return code and light diodes
	int code;
	pid_t pid;
	int returnValue;
	// wait for the termination of any child
	while (1) {
		int pid = waitpid(-1, &code, WNOHANG);

		if (pid <= 0)
			break;

		if (WIFEXITED(code) > 0)
			returnValue = WEXITSTATUS(code);

		// here should open cmd_fifo_s and send data to server 
		if (returnValue == EXIT_FAILURE) {
			char cmd[100];
			// remove trash just in case   
			fprintf(stderr, "an error occured\n");
	
			sprintf(cmd, "rm -f %s/*", PATH_TEMP);
			system(cmd);

			sprintf(cmd, "rm -f %s/*.jpg", PATH_IMAGES);
			system(cmd);
            system("gpio pwm 1 0");
			setStatus(STATUS_ERR);
            break;
		}

		if (pid == main2 && status != STATUS_TERMINATING) {
			setStatus(STATUS_READY);
           
			return;
		} 

		if (returnValue == EXIT_SUCCESS)
			
			setStatus(previousStatus);

	}
	evsignal_add(completedEvent, NULL);
}

//  if SIGINT
void end(evutil_socket_t fd, short what, void *arg)
{
	// turn the lights off and free memory close streams
	setStatus(STATUS_EMPTY);

	event_free(newCmdEvent);
	event_free(completedEvent);
	event_free(termEvent);
	event_free(endEvent);
	event_base_free(base);

	if (close(cmdFifoFd)) {
		perror("close");
		exit(EXIT_FAILURE);
	}
	if (close(cmdFifoSFd)) {
		perror("close");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);
}

int turnOnDiode(int diode)
{
	switch (diode) {
	case WHITE_DIODE:
		system("gpio -g write 4 1");
		break;
	case YELLOW_DIODE:
		system("gpio -g write 24 1");
		break;
	case RED_DIODE:
		system("gpio -g write 25 1");
		break;
	case GREEN_DIODE:
		system("gpio -g write 17 1");
		break;
	default:
		return EXIT_FAILURE;
		break;
	}
	return EXIT_SUCCESS;
}

int turnOffDiode(int diode)
{
	switch (diode) {
	case WHITE_DIODE:
		system("gpio -g write 4 0");
		break;
	case YELLOW_DIODE:
		system("gpio -g write 24 0");
		break;
	case RED_DIODE:
		system("gpio -g write 25 0");
		break;
	case GREEN_DIODE:
		system("gpio -g write 17 0");
		break;
	default:
		return EXIT_FAILURE;
		break;
	}

	return EXIT_SUCCESS;
}

int checkCamera()
{
	char cmd[100];
	int retval;
	sprintf(cmd, "sudo ./PanberryPhoto %s/%d.jpg", PATH_TEMP, rand());
	retval = system(cmd);
	if (WEXITSTATUS(retval) != EXIT_SUCCESS) {
		fprintf(stderr, "Bad camera %d %d %d\n",
			WEXITSTATUS(retval), retval);
		return EXIT_FAILURE;
	} else
		fprintf(stderr, "Camera connection established\n");
	return EXIT_SUCCESS;
}

void setStatus(int s)
{
	int temp;
	temp = status;
	previousStatus = temp;
	status = s;

	fprintf(stderr, "status -> %d\n", s);
	switch (s) {
	case STATUS_READY:
		turnOffDiode(YELLOW_DIODE);
		turnOnDiode(GREEN_DIODE);
		turnOffDiode(RED_DIODE);
        system("gpio pwm 1 0");
          

		break;
	case STATUS_SHOOTING:
	case STATUS_STITCHING:
	case STATUS_ROTATING:
	case STATUS_BUSY:
		turnOffDiode(GREEN_DIODE);
		turnOnDiode(YELLOW_DIODE);
		break;
	case STATUS_NO_CAMERA:
		turnOffDiode(GREEN_DIODE);
		turnOffDiode(YELLOW_DIODE);
		turnOnDiode(RED_DIODE);
		break;
	case STATUS_EMPTY:
		turnOffDiode(WHITE_DIODE);
		turnOffDiode(YELLOW_DIODE);
		turnOffDiode(RED_DIODE);
		turnOffDiode(GREEN_DIODE);
		break;
	case STATUS_ERR:
		turnOffDiode(YELLOW_DIODE);
		turnOnDiode(RED_DIODE);
		turnOffDiode(GREEN_DIODE);
        break;
    case STATUS_TERMINATING:
        turnOffDiode(YELLOW_DIODE);
        usleep(140000);
        turnOnDiode(RED_DIODE);
        usleep(140000);
        turnOffDiode(RED_DIODE);
        turnOnDiode(YELLOW_DIODE);
        system("gpio pwm 1 0");
	default:
		break;
	}
}

void initSystem()
{
	errno = 0;
	system("gpio mode 1 pwm");
	system("gpio pwm-ms");
	system("gpio pwmc 50");

	system("gpio -g mode 4 out");
	system("gpio -g mode 17 out");
	system("gpio -g mode 24 out");
	system("gpio -g mode 25 out");

	system("gpio pwm 1 100");
	sleep(1);
	system("gpio pwm 1 1000");
	sleep(1);
	system("gpio pwm 1 0");
	currentAngle = 180;

	turnOnDiode(WHITE_DIODE);
}

void tryKill(int p)
{
	fprintf(stderr, "SIGKILL %d\n", p);
	if (p != -1 && kill(p, SIGKILL) == -1) {

		if (errno != ESRCH) {
			perror("kill");
			exit(EXIT_FAILURE);
		}

	}

    
}
